﻿using HMS.Contracts.Models;
using HMS.TenantServer.Abstractions;
using HMS.TenantServer.Entities;
using HMS.TenantServer.Extensions;
using HMS.TenantServer.GenerateContext;
using Microsoft.EntityFrameworkCore;
using System.Text;

namespace HMS.TenantServer.Impl;

internal class TenantManager : ITenantManager
{
    private readonly DbContext _context;
    private readonly IServiceProvider _serviceProvider;

    public TenantManager(
        DbContext context,
        IServiceProvider serviceProvider)
    {
        _context = context;
        _serviceProvider = serviceProvider;
    }

    public async Task<string> GetTenantSchemaAsync(string tenantId, CancellationToken cancellationToken = default)
    {
        var tenant = await _context.Set<Tenant>()
            .FirstOrDefaultAsync(x => x.TenantId.Equals(tenantId, StringComparison.OrdinalIgnoreCase), cancellationToken);

        return tenant is not null ? tenant.Schema : string.Empty;
    }

    public Task<TenantModel> LoginAsync(TenantLoginModel model, CancellationToken cancellationToken = default)
    {
        throw new NotImplementedException();
    }

    public async Task<TenantModel> RegisterAsync(string name, CancellationToken cancellationToken = default)
    {
        var tenant = new Tenant
        {
            Name = name,
            License = RandomGenerateLicense(),
            LicenseKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("D"))),
            RootAccount = UniqueIdGenerator(),
            Password = "appeon",
            Schema = UniqueIdGenerator(),
            TenantId = UniqueIdGenerator()
        };

        await using var transcation = await _context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);

        try
        {
            await _context.AddAsync(tenant, cancellationToken);

            var affectedRows = await _context.SaveChangesAsync(cancellationToken);

            if (affectedRows > 0)
            {
                var tenantService = _serviceProvider.GetRequiredService<IScopedTenantService>();

                tenantService.Tenant = tenant;

                using var generatedContext = _serviceProvider.GetRequiredService<TableGenerateContext>();

                await generatedContext.Database.EnsureCreatedAsync(cancellationToken);

                await transcation.CommitAsync(cancellationToken);
            }
        }
        catch (Exception)
        {
            await transcation.RollbackAsync(cancellationToken);
        }

        return tenant.ToModel();
    }

    public async Task<UserModel> RegisterUserAsync(string name, string tenantId, CancellationToken cancellationToken = default)
    {
        var tenant = await _context.Set<Tenant>().FirstOrDefaultAsync(x => x.TenantId.Equals(tenantId), cancellationToken);

        if (tenant is not null)
        {
            var user = new User
            {
                Name = name,
                Account = UniqueIdGenerator(),
                Password = "appeon",
                Tenant = tenant
            };

            await _context.Set<User>().AddAsync(user, cancellationToken);

            await _context.SaveChangesAsync(cancellationToken);

            return user.ToModel();
        }

        throw new InvalidOperationException();
    }

    public async Task<IEnumerable<UserModel>> GetUsersAsync(string tenantId, CancellationToken cancellationToken = default)
    {
        var users = await _context.Set<User>()
            .Include(x => x.Tenant)
            .Where(x => x.Tenant.TenantId == tenantId)
            .ToListAsync(cancellationToken);

        return users.Select(x => x.ToModel());
    }

    public async Task<IEnumerable<TenantModel>> GetTenantsAsync()
    {
        var tenants = new List<TenantModel>();

        await foreach (var item in _context.Set<Tenant>().AsAsyncEnumerable())
        {
            tenants.Add(item.ToModel());
        }

        return tenants;
    }

    private static string UniqueIdGenerator()
    {
        return Guid.NewGuid().ToString("D").Replace("-", "");
    }

    private static string RandomGenerateLicense()
    {
        return __license[Random.Shared.Next(0, 3)];
    }

    private static readonly string[] __license = new string[] { "Professional", "Enterprise", "Ultimate" };
}